﻿using System.Text;
using System.Web;
using System.Security.Cryptography;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;



namespace Kingdee.API.TGTEST
{

    /// <summary>
    /// 云星辰的单据查询接口
    /// </summary>
    public class K3ApiRepositoryV2
    {

        private string ClientID = "268149";
        private string ClientSecret = "d57710a947f43ae6c2f0d4d57b1a5a0c";
        private string outerInstanceId = "227496776318455808";

        /// <summary>
        /// 请求服务器域名,默认http://api.kingdee.com/
        /// </summary>
        private string _baseUrl = "https://api.kingdee.com/";

        /// <summary>
        /// 超时时间（秒）
        /// </summary>
        public int Timeout { get; set; } = 10;

        private AcctInfo acctInfo = null;

        public K3ApiRepositoryV2()
        {
            initToken();
        }




        /// <summary>
        /// 获取销售订单列表api接口
        /// 因为分页限制，便于循环调用获取所有数据，所以单独写。
        /// 这里没写param，正式要写上，主要是分页
        /// 
        /// 保留作为参考，集成到一个方法里
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        public string GetKdSaleListPre(int page)
        {
            // 获取账套信息
            var rt = Execute(new APIRequest
            {
                Path = "/jdy/v2/scm/sal_out_bound",
                Method = HttpMethod.Get,
                Header = new Dictionary<string, string>()
                            {
                                {"app-token",acctInfo.appToken},
                                {"X-GW-Router-Addr",acctInfo.domain},
                            },
                Params = new Dictionary<string, string>()
                {
                    // 参数必须按key排升序，之后参与签名，否则认证错误
                    // 下面的方法已经优化过了。
                   { "page_size","1000" },
                   { "page",page.ToString() },
                },

            }).Result.Content.ReadAsStringAsync().Result;

            return rt;
        }


        public ResponseModel<InstrumentSale> GetKdSaleData()
        {
            // 预先查询单据索引数据
            List<SalOutBoundListReplyRow> billListRows = new List<SalOutBoundListReplyRow>();

            var jSetting = new JsonSerializerSettings();
            jSetting.NullValueHandling = NullValueHandling.Ignore;

            int page = 1;
            while (true)
            {   // 预查寻
                var billListPageStr = Execute(new APIRequest
                {
                    Path = "/jdy/v2/scm/sal_out_bound",
                    Method = HttpMethod.Get,
                    Header = new Dictionary<string, string>()
                            {
                                {"app-token",acctInfo.appToken},
                                {"X-GW-Router-Addr",acctInfo.domain},
                            },
                    Params = new Dictionary<string, string>()
                            {
                               // 参数必须按key排升序，之后参与签名，否则认证错误
                               // 下面的方法已经优化过了。
                               { "page_size","1000" },
                               { "page",page.ToString() },
                            },
                }).Result.Content.ReadAsStringAsync().Result;


                var billListPageObj = JsonConvert.DeserializeObject<SalOutBoundListReply>(JObject.Parse(billListPageStr)["data"].ToString(), jSetting);

                if (billListPageObj.Rows.Count > 0)
                {
                    billListRows.AddRange(billListPageObj.Rows);

                    if (billListPageObj.TotalPage == page)
                    {
                        break;
                    }
                }
                page = page + 1;

            }




            // 二 、 获取单据明细 并组装数据成药监要求格式
            List<InstrumentSale> islist = new List<InstrumentSale>();

            foreach (SalOutBoundListReplyRow billListRow in billListRows)
            {
                var billDetailStr = Execute(new APIRequest
                {
                    Path = "/jdy/v2/scm/sal_out_bound_detail",
                    Method = HttpMethod.Get,
                    Header = new Dictionary<string, string>()
                            {
                                {"app-token",acctInfo.appToken},
                                {"X-GW-Router-Addr",acctInfo.domain},
                            },
                    Params = new Dictionary<string, string>()
                            {
                                    // 参数必须按key排升序，之后参与签名，否则认证错误
                                    // 下面的方法已经优化过了。
                               { "number",billListRow.BillNo },
                            },
                }).Result.Content.ReadAsStringAsync().Result;

                string billEntityStr = JObject.Parse(billDetailStr)["data"].ToString();
                KDInstrumentSale billEntity = JsonConvert.DeserializeObject<KDInstrumentSale>(billEntityStr, jSetting);

                foreach (KDInstrumentSaleEntity kdise in billEntity.material_entity)
                {
                    islist.Add(new InstrumentSale()
                    {
                        SALE_NO = billListRow.BillNo,
                        SALE_DATE = DateTime.Parse(billListRow.BillDate),
                        INSTRUMENT_CODE = kdise.materialid_number,
                        INSTRUMENT_NAME = kdise.materialid_name,
                        REGIST_NO = kdise.custom_entity_field__6__22guspuvyz94__materialid__refistrationnumber,
                        BATCH_NO = kdise.batchno,
                        VALIDITY = kdise.validdate,
                        PRODUCER = kdise.custom_entity_field__6__22dyhzdxn4aubl__materialid__custom_field__4__22dyhvyxiew4bixieq,
                        SPEC = kdise.materialid_model,
                        UNIT = kdise.unit_name,
                        QTY = kdise.qty,
                        PURCHASER_NAME = billListRow.CustomerName,
                        PURCHASER_CODE = billListRow.CustomerNumber,
                        DESTROY_NO = ""
                    });
                }
            }

            ResponseModel<InstrumentSale> tmp3 = new ResponseModel<InstrumentSale>(false, "失败");
            tmp3.data = islist;
            tmp3.success = true;
            tmp3.message = "成功";
            return tmp3;
        }




        /// <summary>
        /// 认证获取token
        /// </summary>
        /// <returns></returns>
        public bool initToken()
        {
            bool islogin = false;

            // 获取账套信息
            string rt = Execute(new APIRequest
            {
                Path = "/jdyconnector/app_management/push_app_authorize",
                Method = HttpMethod.Post,
                Params = new Dictionary<string, string>()
                {
                    {"outerInstanceId",outerInstanceId},

                }
            }).Result.Content.ReadAsStringAsync().Result;


            // 获取token
            if (200 == JObject.Parse(rt)["code"].Value<int>())
            {
                var jSetting = new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Ignore,
                    MissingMemberHandling = MissingMemberHandling.Ignore
                };
                acctInfo = JsonConvert.DeserializeObject<AcctInfo>(JArray.Parse(JObject.Parse(rt)["data"].ToString()).First.ToString(), jSetting);

                // 获取授权信息
                string rt1 = Execute(new APIRequest
                {
                    Path = "/jdyconnector/app_management/kingdee_auth_token",
                    Method = HttpMethod.Get,
                    Params = new Dictionary<string, string>()
                    {
                        {"app_key",acctInfo.appKey},
                        {"app_signature",HmacSHA256(acctInfo.appSecret, acctInfo.appKey)},
                    }
                }).Result.Content.ReadAsStringAsync().Result;

                if (0 == JObject.Parse(rt1)["errcode"].Value<int>())
                {
                    acctInfo.accessToken = JObject.Parse(rt1)["data"]["access_token"].Value<string>();
                    acctInfo.appToken = JObject.Parse(rt1)["data"]["app-token"].Value<string>();
                    islogin = true;
                }
            }

            return islogin;
        }


        /// <summary>
        /// 发起请求
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> Execute(APIRequest request)
        {
            using (HttpClient _client = new HttpClient())
            {

                _client.Timeout = TimeSpan.FromSeconds(Timeout);
                BuildRequestHeader(_client.DefaultRequestHeaders, request);


                var uri = _baseUrl.TrimEnd('/') + "/" + request.Path + "?" + GetQueryString(request);

                if (request.Method == HttpMethod.Post)
                {
                    if (string.IsNullOrEmpty(request.File))
                    {
                        //string rt = _client.PostAsync(requestUri, new StringContent(string.Empty, Encoding.UTF8, "application/json")).Result.Content.ReadAsStringAsync().Result;

                        var json = request.Body == null ? string.Empty : System.Text.Json.JsonSerializer.Serialize(request.Body);
                        return await _client.PostAsync(uri, new StringContent(json, Encoding.UTF8, request.ContentType));

                    }
                    else
                    {
                        //文件上传
                        string boundary = $"----WebKitFormBoundary{DateTime.Now.Ticks:x}";
                        MultipartFormDataContent content = new MultipartFormDataContent(boundary);
                        if (!File.Exists(request.File))
                        {
                            throw new Exception($"路径不存在:{request.File}");
                        }

                        using (FileStream fStream = File.Open(request.File, FileMode.Open, FileAccess.Read))
                        {
                            content.Add(new StreamContent(fStream, (int)fStream.Length), "file", Path.GetFileName(request.File));
                            return await _client.PostAsync(uri, content);
                        }
                    }
                }
                else if (request.Method == HttpMethod.Get)
                {
                    return await _client.GetAsync(uri);
                }
                else if (request.Method == HttpMethod.Put)
                {
                    var json = request.Body == null ? string.Empty : System.Text.Json.JsonSerializer.Serialize(request.Body);
                    return await _client.PutAsync(uri, new StringContent(json, Encoding.UTF8, request.ContentType));
                }
                else if (request.Method == HttpMethod.Delete)
                {
                    return await _client.DeleteAsync(uri);
                }
                else
                {
                    throw new Exception($"Method:{request.Method} 参数不支持!");
                }

            }
        }


        private void BuildRequestHeader(HttpRequestHeaders headers, APIRequest request)
        {
            var api_nonce = new Random().Next(100000000, 999999999);
            var timeStamp = (int)(DateTime.Now.ToLocalTime() - DateTime.Parse("1970-01-01").ToLocalTime()).TotalSeconds;

            headers.Add("X-Api-Nonce", api_nonce.ToString());
            headers.Add("X-Api-Auth-Version", "2.0");
            headers.Add("X-Api-TimeStamp", timeStamp.ToString());
            headers.Add("X-Api-ClientID", ClientID);
            headers.Add("X-Api-Signature", GetSignature(request, api_nonce.ToString(), timeStamp.ToString()));
            headers.Add("X-Api-SignHeaders", "X-Api-Nonce,X-Api-TimeStamp");


            if (request.Header.Any())
            {
                foreach (var hk in request.Header.Keys)
                {
                    headers.Add(hk, request.Header[hk]);
                }
            }
        }


        private string GetSignature(APIRequest request, string nonce, string timestamp)
        {

            var querySign = GetQueryString(request, true);

            StringBuilder sb = new StringBuilder();
            sb.Append(request.Method.ToString().ToUpper());
            sb.Append('\n');
            sb.Append(PathEncode(request.Path));
            sb.Append('\n');
            sb.Append(querySign);
            sb.Append('\n');
            sb.Append($"x-api-nonce:{nonce}");
            sb.Append('\n');
            sb.Append($"x-api-timestamp:{timestamp}");
            sb.Append('\n');
            return HmacSHA256(ClientSecret, sb.ToString());
        }

        //！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！

        // 原方法严重bug，没有考虑参数排序的问题，影响签名字符串。所以改了

        //！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！

        /* 
            参考https://open.jdy.com/#/files/api/detail?index=3&categrayId=5403e0fd6a5811eda819b759130d6d33&id=b5c26557da9d11ed8e36f9799578d2e1

            X-Api-Signature生成规则

            准备签名原文：
            请求方式（POST、GET、DELETE等均为大写）

            请求地址的path部分（示例：https://api.kingdee.com/jdyconnector/app_management/kingdee_auth_token，取红色部分，进行url编码）

            params请求参数（进行两次url编码（编码后字母应是大写，下同），加密时按参数名ASCII码升序顺序进行排序，实际请求参数顺序必须一致）

            headers请求参数（只需要x-api-nonce、x-api-timestamp参与加密，必须小写）

            拼接得到签名原文，每段签名字符拼接均需要换行符（x-api-timestamp时间戳后需要换行符）
        */


        /// <summary>
        /// 查询字符串部分拼接,区分一次加密参数和二次加密参数
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private static string GetQueryString(APIRequest request, bool doubleEncode = false)
        {
            var querySign = string.Empty;


            if (request.Params != null && request.Params.Keys != null)
            {
                // 先来个排序
                // 版权声明：本文为CSDN博主「heyuchang666」的原创文章，遵循CC 4.0 BY - SA版权协议，转载请附上原文出处链接及本声明。
                // 原文链接：https://blog.csdn.net/heyuchang666/article/details/49683349
                // foreach (KeyValuePair<string, int> kvp in lst)
                // foreach (var key in request.Params.Keys)

                List<KeyValuePair<string, string>> lst = new List<KeyValuePair<string, string>>(request.Params);
                lst.Sort(delegate (KeyValuePair<string, string> s1, KeyValuePair<string, string> s2)
                {
                    return s1.Key.CompareTo(s2.Key);
                });

                foreach (KeyValuePair<string, string> item in lst)
                {
                    if (doubleEncode)
                    {
                        querySign = querySign
                                + $"&{item.Key}="
                                + HttpUtility.UrlEncode(HttpUtility.UrlEncode(item.Value));
                    }
                    else
                    {
                        querySign = querySign
                                + $"&{item.Key}="
                                + HttpUtility.UrlEncode(item.Value);
                    }
                }
                querySign = querySign.TrimStart('&');
            }

            return querySign;
        }


        /// <summary>
        /// Url Path部分Url编码
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string PathEncode(string path) => HttpUtility.UrlEncode("/" + path.TrimStart('/'))
                    .Replace("%2f", "%2F")
                    .Replace("%2d", "%2D")
                    .Replace("(", "%28")
                    .Replace(")", "%29");

        /// <summary>
        /// 实现HMAC SHA256签名算法
        /// </summary>
        /// <param name="secret">加密秘钥</param>
        /// <param name="signContent">加密内容</param>
        /// <returns></returns>
        public string HmacSHA256(string secret, string signContent)
        {
            string signRet = string.Empty;
            using (HMACSHA256 mac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
            {
                byte[] hash = mac.ComputeHash(Encoding.UTF8.GetBytes(signContent));
                signRet = ToHexString(hash);
            }
            var hexBytes = Encoding.UTF8.GetBytes(signRet);
            return Convert.ToBase64String(hexBytes);
        }

        /// <summary>
        ///  转16进制
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        protected string ToHexString(byte[] bytes)
        {
            string hexString = string.Empty;
            if (bytes != null)
            {
                StringBuilder strB = new StringBuilder();
                foreach (byte b in bytes)
                {
                    strB.AppendFormat("{0:x2}", b);
                }
                hexString = strB.ToString();
            }
            return hexString;
        }


    }
}
